package math.rand;

import java.util.Random;
import math.mat3;
import math.math;
import math.vec2;
import math.vec3;
import static math.vec3.*;

public class RNG
{
    
    private final long mSeed;
    
    private final Random mRand;
    
    
    public RNG(long aSeed)
    {
        this.mSeed = aSeed;
        this.mRand = new Random(aSeed);
    }
    
    
    public final RNG copy()
    {
        RNG copy = new RNG(this.mSeed);
        return copy;
    }
    
    
    public final int integer(int aUpperBound)
    {
        return this.mRand.nextInt(aUpperBound);
    }
    
    public final int integer(int aLo, int aHi)
    {
        return aLo + this.mRand.nextInt(aHi - aLo);
    }
    
    
    public final double rand1f()
    {
        return this.mRand.nextDouble();
    }
    
    public final double rand1f(double aLo, double aHi)
    {
        return math.lerp(aLo, aHi, this.rand1f());
    }
    
    public final vec2 rand2f()
    {
        return new vec2(
            this.rand1f(), 
            this.rand1f()
        );
    }
    
    public final vec2 rand2f(double aLo, double aHi)
    {
        return new vec2(
            this.rand1f(aLo, aHi), 
            this.rand1f(aLo, aHi)
        );
    }
    
    public final vec3 rand3f()
    {
        return new vec3(
            this.rand1f(), 
            this.rand1f(), 
            this.rand1f()
        );
    }
    
    public final vec3 rand3f(double aLo, double aHi)
    {
        return new vec3(
            this.rand1f(aLo, aHi), 
            this.rand1f(aLo, aHi), 
            this.rand1f(aLo, aHi)
        );
    }
    
    public final double gauss(double aMean, double aDeviation)
    {
        double rgauss = this.mRand.nextGaussian();
        return aMean + (aDeviation * rgauss);
    }
    
    
    public final vec2 unitDisk()
    {
        double rtR = math.sqrt(this.rand1f());
        double theta = 2.0 * math.PI * this.rand1f();
        vec2 rand = new vec2(
            rtR * math.cos(theta), 
            rtR * math.sin(theta)
        );
        return rand;
    }
    
    public final vec3 unitSphere()
    {
        vec3 rand = new vec3(
            this.gauss(0, 1), 
            this.gauss(0, 1), 
            this.gauss(0, 1)
        );
        rand.norm();
        return rand;
    }
    
    public final vec3 unitHemisphere(vec3 aPole)
    {
        vec3 rand = this.unitSphere();
        boolean onWrongSide = dot(rand, aPole) <= 0;
        if (onWrongSide)
        {
            rand.mul(-1.0);
        }
        return rand;
    }
    
    public final vec3 unitCosHemisphere(vec3 aPole)
    {
        vec3 basis0 = vec3.perp(aPole);
        double theta0 = 0.5 * math.PI * this.rand1f(-1.0, 1.0);
        double theta1 = 2.0 * math.PI * this.rand1f();
        mat3 rot0 = mat3.rotate(basis0, theta0);
        mat3 rot1 = mat3.rotate(aPole, theta1);
        vec3 rand0 = rot0.mul(aPole);
        vec3 rand1 = rot1.mul(rand0);
        return rand1;
    }
    
    public final vec3 unitCone(
            vec3 aPole, 
            double aAngleDeg)
    {
        double rtR = math.sqrt(this.rand1f());
        double theta = 2.0 * math.PI * this.rand1f();
        double x = rtR * math.cos(theta);
        double y = rtR * math.sin(theta);
        double tan = math.tan(math.toRad(aAngleDeg));
        vec3 basis0 = vec3.perp(aPole);
        vec3 basis1 = vec3.cross(aPole, basis0);
        basis0.mul(x * tan);
        basis1.mul(y * tan);
        vec3 rand = vec3.add(aPole, vec3.add(basis0, basis1));
        rand.norm();
        return rand;
    }
    
    public final vec3 unitDisk(vec3 aPole)
    {
        vec3 basis0 = vec3.perp(aPole);
        double rtR = math.sqrt(this.rand1f());
        double theta = 2.0 * math.PI * this.rand1f();
        mat3 rot = mat3.rotate(aPole, theta);
        vec3 rand = rot.mul(basis0);
        rand.norm();
        rand.mul(rtR);
        return rand;
    }
    
}
